package simpleftp.client.control;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CompletionService;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.prefs.BackingStoreException;
import java.util.prefs.Preferences;

import javax.swing.JFileChooser;
import javax.swing.JOptionPane;
import javax.swing.JTree;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.MutableTreeNode;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreeNode;



import simpleftp.client.gui.FTPClientWindow;

public class CommandDispatcher implements Runnable
{  public static final String NL = "\n";
    private static final String USER = "USER";
    private static final String PASS = "PASS";
    private static final String SERVER = "SERVER";
    private static final String PORT = "PORT";
    private static final String SETTINGS_ROOT = "simpleftpsettings";
    private static final String DEFAULT_SETTINGS = SETTINGS_ROOT + "/default";
    private static final String LAST_SETTINGS = SETTINGS_ROOT + "/last";
    private static final String DEF_USER = "paris";
    private static final String DEF_PASS = "paris";
    private static final String DEF_SERVER = "127.0.0.1";
    private static final String DEF_PORT = "7802";
    private final ExecutorService executorPool;
    private final CompletionService<Boolean> completionPool;
    private final FTPClientWindow window;
    private final int numberOfCores;
    private final double blockingCoefficient = 0.9;
    private final int poolSize;
    private BlockingQueue<Command> commands;
    private SimpleFTP client;
    private Preferences userPrefs;
    private String server;
    private String port;
    private String user;
    private String pass;
    private boolean alreadyConnected;

    public CommandDispatcher(BlockingQueue<Command> cmd, FTPClientWindow wind) {
        this.commands = cmd;
        this.window = wind;
        alreadyConnected = false;
        numberOfCores = Runtime.getRuntime().availableProcessors();
        poolSize = (int) (numberOfCores / (1 - blockingCoefficient));
        executorPool = Executors.newFixedThreadPool(poolSize);
        completionPool = new ExecutorCompletionService<Boolean>(executorPool);
        client = new SimpleFTP();
    }

    @Override
    public void run() {
        try {
            loadStartupPreferences();
            Command cmd = this.commands.take();
            while (!cmd.equals(Command.QUIT)) {
                switch (cmd) {
                    case CONNECT:
                        connect();
                        break;
                    case DISCONNECT:
                        disconnect();
                        break;
                    case STOR:
                        upload();
                        break;
                    case LIST:
                        listDir();
                        break;
                    case PASV:
                        download();
                        break;
                    case PWD:
                        pwd();
                        break;
                    case CWD :
                        cwd();
                        break;
                    case CLEAR:
                        clearConsole();
                        break;
                    case LOADPREFS:
                        loadPreferences();
                        break;
                    case SAVEPREFS:
                        savePreferences();
                        break;
                    case DELPREF:
                        deletePreference();
                        break;
                    default:
                        break;
                }
                cmd = this.commands.take();
            }
            saveLastPreferences();
            disconnect();
            executorPool.shutdown();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private void clearConsole() {
        window.console.setText("");
    }

    private void saveLastPreferences() {
        userPrefs = Preferences.userRoot().node(LAST_SETTINGS);
        userPrefs.put(USER, window.tfusername.getText());
        userPrefs.put(PASS, new String(window.pfuserpass.getPassword()));
        userPrefs.put(SERVER, window.tfservername.getText());
        userPrefs.put(PORT, window.tfport.getText());
        try {
            userPrefs.flush();
        } catch (BackingStoreException e) {
            e.printStackTrace();
        }
    }

    private void loadStartupPreferences() {
        try {
            userPrefs = Preferences.userRoot();
            if (!userPrefs.nodeExists(DEFAULT_SETTINGS)) {
                userPrefs = Preferences.userRoot().node(DEFAULT_SETTINGS);
                userPrefs.put(USER, DEF_USER);
                userPrefs.put(PASS, DEF_PASS);
                userPrefs.put(SERVER, DEF_SERVER);
                userPrefs.put(PORT, DEF_PORT);
                userPrefs.flush();
            }
            if (userPrefs.nodeExists(LAST_SETTINGS)) {
                userPrefs = Preferences.userRoot().node(LAST_SETTINGS);
            }
            window.tfusername.setText(userPrefs.get(USER, DEF_USER));
            window.pfuserpass.setText(userPrefs.get(PASS, DEF_PASS));
            window.tfservername.setText(userPrefs.get(SERVER, DEF_SERVER));
            window.tfport.setText(userPrefs.get(PORT, DEF_PORT));
        } catch (BackingStoreException e) {
            e.printStackTrace();
        }
    }

    private void loadPreferences() {
        try {
            userPrefs = Preferences.userRoot().node(SETTINGS_ROOT);
            String[] userSavedPrefs = userPrefs.childrenNames();
            String s = (String) JOptionPane.showInputDialog(window,
                    "Choose preference to load:", "Load Preferences",
                    JOptionPane.QUESTION_MESSAGE, null, userSavedPrefs, userSavedPrefs[0]);
            if ((s != null) && (s.length() > 0)) {
                userPrefs = Preferences.userRoot().node(SETTINGS_ROOT + "/" + s);
                window.tfusername.setText(userPrefs.get(USER, DEF_USER));
                window.pfuserpass.setText(userPrefs.get(PASS, DEF_PASS));
                window.tfservername.setText(userPrefs.get(SERVER, DEF_SERVER));
                window.tfport.setText(userPrefs.get(PORT, DEF_PORT));

                window.console.append(NL + "User preferences " + s + " loaded.");
            } else {
                window.console.append(NL + "Preferences loading operation cancelled.");
            }
        } catch (BackingStoreException e) {
            e.printStackTrace();
        }
    }

    private void savePreferences() {
        String s = (String) JOptionPane.showInputDialog(window,
                "Save the current connection info as:", "Save Preferences",
                JOptionPane.QUESTION_MESSAGE, null, null, null);
        if ((s != null) && (s.length() > 0)) {
            userPrefs = Preferences.userRoot().node(SETTINGS_ROOT + "/" + s);
            userPrefs.put(USER, window.tfusername.getText());
            userPrefs.put(PASS, new String(window.pfuserpass.getPassword()));
            userPrefs.put(SERVER, window.tfservername.getText());
            userPrefs.put(PORT, window.tfport.getText());
            try {
                userPrefs.flush();
            } catch (BackingStoreException e) {
                window.console.append(NL + "Could not save preferences: "
                        + e.getMessage());
                e.printStackTrace();
            }
            window.console.append(NL + "Preferences saved as " + s);
        } else {
            window.console.append(NL + "Preferences saving operation cancelled.");
        }
    }

    private void deletePreference() {
        try {
            userPrefs = Preferences.userRoot().node(SETTINGS_ROOT);
            String[] userSavedPrefs = userPrefs.childrenNames();
            String s = (String) JOptionPane.showInputDialog(window,
                    "Choose preference to delete:", "Delete Preference",
                    JOptionPane.QUESTION_MESSAGE, null, userSavedPrefs, userSavedPrefs[0]);
            if ((s != null) && (s.length() > 0)) {
                userPrefs = Preferences.userRoot().node(SETTINGS_ROOT + "/" + s);
                userPrefs.removeNode();
                userPrefs.flush();
                window.console.append(NL + "User preferences " + s + " deleted.");
            } else {
                window.console.append(NL + "Preferences deletion operation cancelled.");
            }
        } catch (BackingStoreException e) {
            e.printStackTrace();
        }
    }

    private void download() {
        window.console.append(NL + "Command not yet supported");
        throw new UnsupportedOperationException("Download operation to be implemented...");
        
        ////////////////////////////////////
        
        //select file and call Downloader class
        //call it for all the file selected
        //and after propose like upload a place to put the file
        
        /////
    }

    private void upload() {
        if (!alreadyConnected) {
            window.console.append(NL + "You are not connected to any server.");
            return;
        }
        final JFileChooser fc = new JFileChooser();
        fc.setFileSelectionMode(JFileChooser.FILES_ONLY);
        fc.setMultiSelectionEnabled(true);
        fc.setDragEnabled(true);
        int returnVal = fc.showDialog(window, "Upload");
        if (returnVal == JFileChooser.APPROVE_OPTION) {
            File[] files = fc.getSelectedFiles();
            List<Future<Boolean>> uploaders = new ArrayList<Future<Boolean>>(
                    files.length);
            
            //for each upload file create a new thread for transfert
            for (File f : files) {
                Uploader uploader = new Uploader(f, window, server, port, user,
                        pass);
                uploaders.add(completionPool.submit(uploader)); 
            }
            listDir();
            window.tree.updateUI();
        } else {
            window.console.append(NL + "Upload action cancelled.");
        }
    }

    //get tree directory informations
    private void listDir() {
        if (!alreadyConnected) {
            window.console.append(NL + "You are not connected to any server.");
            return;
        }
        try {
            String dirContent = client.ls();
            window.console.append(NL + dirContent);
            // File f = client.getFileTree(); JFT
            window.root.removeAllChildren();
            // DefaultMutableTreeNode userRoot = new DefaultMutableTreeNode(f.getName()); JFT
            // populateTree(userRoot, f); JFT

            // receive serialized JTREE as MutableTreeNode
            MutableTreeNode userRoot = client.getMutableTreeNode();
            window.root.add(userRoot);
        } catch (IOException e) {
            window.console.append(NL + e.getMessage());
            e.printStackTrace();
        }

    }

    private void populateTree(DefaultMutableTreeNode userRoot, File root) {
        // if use json or txt file
    }

    //get actual directory
    private void pwd() {
        try {
            if (!alreadyConnected) {
                window.console.append(NL + "You are not connected to any server.");
                return;
            }
            String curDir = client.pwd();
            window.console.append(NL + "Current directory on FTP server: " + curDir);
        } catch (IOException e) {
            window.console.append(NL + e.getMessage());
            e.printStackTrace();
        }
    }

    //change tree to the new main directory
    private void cwd(){
        if(window.tree.getSelectionModel().getSelectionCount() == 1){
            String PathDestination = window.tree.getSelectionModel().getSelectionPath().toString();
            PathDestination = PathDestination.substring(PathDestination.lastIndexOf(", ")+2, PathDestination.lastIndexOf("]"));
            System.out.println("0000 : "+PathDestination);
            try {
                client.cwd(PathDestination);
                listDir();
                window.tree.updateUI();
            } catch (IOException e) {
                e.printStackTrace();
            }


        }
    }

    private void disconnect() {
        try {
            if (alreadyConnected) {
                client.disconnect();
                alreadyConnected = false;
                window.console.append(NL + "Now disconnected");
            } else {
                window.console.append(NL + "Already disconnected");
            }
        } catch (IOException e) {
            window.console.append(NL + e.getMessage());
            e.printStackTrace();
        }

    }

    private void connect() {
        if (alreadyConnected) {
            window.console.append(NL
                    + "You are already connected to this server.");
            return;
        }
        server = window.tfservername.getText();
        if (server == null || "".equals(server)) {
            window.console.append(NL
                    + "You must specify the server IP address.");
            return;
        }
        port = window.tfport.getText();
        if (port == null || "".equals(port)) {
            window.console.append(NL
                    + "You must specify the server port I must connect to.");
            return;
        }
        user = window.tfusername.getText();
        if (user == null || "".equals(user))
            user = "anonymous";
        pass = new String(window.pfuserpass.getPassword());
        if (pass == null || "".equals(pass))
            pass = "anonymous";
        try {
            client.connect(server, Integer.parseInt(port), user, pass);
            alreadyConnected = true;
            window.console.append(NL + "Now connected");
            pwd();
            listDir();
        } catch (IOException e) {
            window.console.append(NL + e.getMessage());
            e.printStackTrace();
        } catch (NumberFormatException nfe) {
            window.console.append(NL + nfe.getMessage());
            nfe.printStackTrace();
        }
    }


    @SuppressWarnings(value = { "hh" })
    private void debugJTree(){
        JTree tree = new JTree(client.getMutableTreeNode());

        TreeModel model = tree.getModel();

        DefaultMutableTreeNode rootNode = (DefaultMutableTreeNode) model.getRoot();
        // Just changing enumeration kind here
        Enumeration<DefaultMutableTreeNode> en = rootNode.preorderEnumeration();
        while (en.hasMoreElements())
        {
            DefaultMutableTreeNode node = en.nextElement();
            TreeNode[] path = node.getPath();
            System.out.println((node.isLeaf() ? "  - " : "+ ") + path[path.length - 1]);
        }
    }
}
